package root.user;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.management.AttributeNotFoundException;

import root.SessionManager;
import root.item.Item;
import root.item.PublicarPedido;
import strategy.AbstractStrategyFactory;
import exceptions.AbsentAtributException;
import exceptions.BorrowDoesNotExist;
import exceptions.HasAlreadyDoneException;
import exceptions.InexistentCategory;
import exceptions.InexistentTopic;
import exceptions.InvalidAtributException;
import exceptions.InvalidBorrowId;
import exceptions.InvalidCategory;
import exceptions.InvalidDurationException;
import exceptions.InvalidKeyException;
import exceptions.InvalidMessage;
import exceptions.InvalidReceiver;
import exceptions.InvalidSender;
import exceptions.InvalidSession;
import exceptions.InvalidSubject;
import exceptions.InvalidUserAddress;
import exceptions.InvalidUserException;
import exceptions.InvalidUserName;
import exceptions.InvalidUserUsername;
import exceptions.IsNotOnwerException;
import exceptions.ItemDoesNotExist;
import exceptions.ItemRequestAlreadyExistsException;
import exceptions.PermissionDeniedException;
import exceptions.SessionDoesNotExist;
import exceptions.UserAlreadyExistsException;
import exceptions.UserNotExistsException;
import exceptions.WithoutPermissionRequestLoanException;


/**
 * Classe montada para gerenciar os usuarios.
 * Relaciona as operacoes sobre usuarios e
 * utiliza o padrao Singleton.
 */
public class UserManager{
        private List<User> listUsers = new ArrayList<User>();
        private static UserManager userManager;
        
        /**
         * (Singleton)Retorna a unica instancia de UserManager
         * @return
         */
        public static UserManager getUserManager(){
                if (userManager == null)
                        userManager = new UserManager();
                return userManager;
                
        }
        /**
         * Cria um usuario
         * @param login
         *      Login do usuario
         * @param name
         *      Nome do usuario
         * @param address
         *      Endereco do Usuario
         * @return
         *      Codigo identificador do usuario
         * @throws InvalidUserUsername
         *      Se o lgoin eh invalido( null ou vazio)
         * @throws InvalidUserName
         *      Se o nome é inválido(null ou vazio)
         * @throws InvalidUserAddress
         *      Se o endereço é inválido(null ou vazio)
         * @throws UserAlreadyExistsException
         *      Se já existe um usuário com o mesmo login no sistema
         * @throws InvalidUserException
         */
        public User createUser(String login, String name, String address) throws InvalidUserUsername, InvalidUserName, InvalidUserAddress, UserAlreadyExistsException, InvalidUserException{
                User usr = new User(login, name, address, AbstractStrategyFactory.getInstance());
                addUser(usr);
                return usr;
                
        }
        
        
        private void addUser(User user) throws UserAlreadyExistsException, InvalidUserException {
                if(user==null){
                        throw new InvalidUserException();
                }
                if(hasUser(user.getUsername())){
                                throw new UserAlreadyExistsException("Já existe um usuário com este login");
                }
                listUsers.add(user);
        }
        
        /**
         * Verifica se determinado usuario está no sistema
         * @param userName
         *      Login do usuário
         * @return
         *      True se está no sistema.
         */
        public boolean hasUser(String userName) {
                int index=getIndexByUserLogin(userName);
                if(index==-1){
                        return false;
                }
                else{
                        return true;
                }
                
        }
        /**
         * retorna o indice do user se não existir retorn -1
         */
        private int getIndexByUserLogin(String login){
                Iterator<User> it = listUsers.iterator();
                int resp=-1;
                int cont=0;
                while(it.hasNext()){
                        if(it.next().getUsername().equals(login)){
                                resp=cont;
                        }
                        cont++;
                }
                return resp;
        }
        /**
         * Remove determinado usuario do sistema.
         * @param login
         *      Login do usuario a ser removido
         * @throws UserNotExistsException
         *      Se o usuario não existir no sistema
         */
        public void removeUser(String login) throws UserNotExistsException {
                int key = getIndexByUserLogin(login);
                
                if(key==-1){
                        throw new UserNotExistsException("Usuário inexistente");
                }
                listUsers.remove(key);
                
        }
        
        
        /**
         * Retorna o usuário que possui determinado login
         * @param login
         * @return
         * @throws UserNotExistsException
         */
        public User getUser(String login) throws UserNotExistsException{
                int key = getIndexByUserLogin(login);
                if (login == null || login.isEmpty())
                        throw new UserNotExistsException("Login inválido");
                if(key==-1){
                                throw new UserNotExistsException("Usuário inexistente");
                }
                return listUsers.get(key);
        }
        
        
        /**
         * Retorna determinado atributo de um usuário
         * @param login
         *      Login do usuário
         * @param atributo
         *      "nome" ou "endereco"
         * @return
         * @throws InvalidUserUsername
         * @throws UserNotExistsException
         * @throws InvalidAtributException
         * @throws AttributeNotFoundException
         */
        public String getAttribute(String login,String atributo) throws InvalidUserUsername, UserNotExistsException, InvalidAtributException, AttributeNotFoundException{
                String resp="";
                int key = getIndexByUserLogin(login);
                
                if(login==null||login.isEmpty()){
                        throw new InvalidUserUsername("Login inválido");
                }
                if(key==-1){
                        throw new UserNotExistsException("Usuário inexistente");
                }
                User userAux = listUsers.get(key);
                if(atributo==null || atributo.isEmpty()){
                        throw new InvalidAtributException("Atributo inválido");
                }
                if(atributo.equals("nome")){
                        resp= userAux.getName();
                }
                else if(atributo.equals("endereco")){
                        resp= userAux.getAddress();
                }
                else{
                        throw new AttributeNotFoundException("Atributo inexistente");
                }
                return resp;
        }
        
        /**
         * Retorna uma lista de usuarios que satisfacam determinada condicao.
         * chave deve estar contido em atributo.
         * O usuario que possui idSessao nao aparece nessa lista
         * @param idSessao
         *      Sessao do usuario que requisitou.
         * @param chave
         *      Chave a ser procurada
         * @param atributo
 *      Atributos válidos: "nome" e "endereco"
         * @return
         *      Uma string contendo todos os usuarios que satisfacam essas condicoes.
         * @throws InvalidSession
         * @throws InvalidAtributException
         * @throws SessionDoesNotExist
         * @throws InvalidKeyException
         * @throws UserNotExistsException
         */
        public String findUser(String idSessao,String chave,String atributo) throws InvalidSession, InvalidAtributException, SessionDoesNotExist, InvalidKeyException, UserNotExistsException{
                
                String resp="";
                if(atributo==null||atributo.isEmpty()){
                        throw new InvalidAtributException("Atributo inválido");
                }

                else if (chave == null || chave.isEmpty()) {
                        throw new InvalidKeyException("Palavra-chave inválida");
                        
                }
                else if (atributo.equals("nome")) {
                        String login  = SessionManager.getSessionManager().getSession(idSessao).getUsername();
                        resp=findByName(login, chave);
                }
                else if (atributo.equals("endereco")) {
                        String login  = SessionManager.getSessionManager().getSession(idSessao).getUsername();
                        resp = findByAddress(login, chave);
                }
                else {
                        throw new InvalidAtributException("Atributo inexistente");
                        
                }
                
                
                
                return resp;
                
        }
        
        private String toText(Iterator<User> i) {
        	String ret = "";
        	User aux;
        	while(i.hasNext()) {
        		aux = i.next();
        		ret = ret.concat(aux.toString() + "; ");
        	}
        	if(ret.endsWith("; "))
        		ret = ret.substring(0, ret.length()-2);
        	return ret;
        }
        
        public String findUser(String sessionID) 
        throws SessionDoesNotExist, InvalidSession, UserNotExistsException{
        	String text = "";
        	User caller = getUser(SessionManager.getSessionManager().getSession(sessionID).getLogin());
        	List<User> users = new ArrayList<User>(listUsers);
        	users.remove(caller);
        	Collections.sort(users, new UserDistanceComparator(caller));
        	if(!users.isEmpty())
        		text = toText(users.iterator());
        	else
        		text = "Nenhum usuário encontrado";
        	return text;
        }

        private String findByName(String login, String chave) throws UserNotExistsException {
                String resp ="";
                Iterator<User> it = listUsers.iterator();
                boolean occurred = false;
                User aux;
                while(it.hasNext()){
                        aux=it.next();
                
                        if(!aux.getUsername().equals(login)&&aux.getName().contains(chave)){
                                if (occurred)
                                        resp =resp.concat( "; ");
                                occurred = true;
                                resp=resp.concat(aux.toString());
                        }
                        
                }               
                if (!occurred)
                        return ("Nenhum usuário encontrado");
                
                return resp;
        }
        
        private String findByAddress(String login, String chave) throws UserNotExistsException {
                String resp ="";
                Iterator<User> it = listUsers.iterator();
                User aux;
                boolean occurred = false;
                while(it.hasNext()){
                        aux=it.next();
                        if(!aux.getUsername().equals(login)&&aux.getAddress().contains(chave)){
                                if (occurred)
                                        resp = resp.concat("; ");
                                occurred = true;
                                resp = resp.concat(aux.toString());
                        }
                        
                }               
                if (!occurred)
                        return ("Nenhum usuário encontrado");
                
                return resp;
        }
        
        /**
         * Retorna uma string contendo os usuarios amigos de user ordenados pelo ranking
         * de emprestimos completados ou global se for pedido.
         * @param user
         *      Pediu a informacao
         * @param tipo
         *      Se quer um rank "global" ou apenas dos "amigos"
         * @return
         * @throws InvalidCategory
         * @throws InexistentCategory
         */
        public String getRanking(User user, String tipo) throws InvalidCategory, InexistentCategory {
                if (tipo == null || tipo.isEmpty()) {
                        throw new InvalidCategory("Categoria inválida");
                        
                }
                List<User> ret;
                if (tipo.equals("global")) {
                        ret = new ArrayList<User>(listUsers);
                }
                else if (tipo.equals("amigos")) {
                        ret =  new ArrayList<User>(user.getFriends());
                        ret.add(user);
                }
                else
                        throw new InexistentCategory("Categoria inexistente");
                Collections.sort( ret );
                Iterator<User> it = ret.iterator();
                
                boolean ok = false;
                String willRet = "";
                while (it.hasNext()) {
                        User next = it.next();
                        if(ok) {
                                willRet =willRet.concat( "; ");
                        }
                        ok = true;
                        willRet = willRet.concat(next.getUsername());
                }
                return willRet;
        }

        public static void setInstance(UserManager userM) {
                userManager = userM;
                
        }       
                
        public boolean hasBorrowId(String borrowId){
                boolean has = false;
                for(User user : listUsers){
                        if(user.hasBorrow(borrowId)) return true;
                }
                return has;
        }
        
        public void returnItem(User user, String borrowId) throws PermissionDeniedException, HasAlreadyDoneException, BorrowDoesNotExist, InvalidBorrowId{
                try{
                        user.returnItem(borrowId);
                } catch (BorrowDoesNotExist e){
                        if(hasBorrowId(borrowId)) throw new PermissionDeniedException("O item só pode ser devolvido pelo usuário beneficiado");
                        throw new BorrowDoesNotExist(e.getMessage());
                }
        }
        
        public void requestDevolution( User user , String borrowId) throws InvalidBorrowId, HasAlreadyDoneException, PermissionDeniedException, ItemDoesNotExist, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver, BorrowDoesNotExist{
                try{
                        user.requestDevolution(borrowId);
                } catch ( BorrowDoesNotExist e){
                        if(hasBorrowId(borrowId)) throw new HasAlreadyDoneException("O usuário não tem permissão para requisitar a devolução deste item");
                        throw new BorrowDoesNotExist(e.getMessage());
                }
        }
        
        public void completeLoan(User user, String borrowId) throws PermissionDeniedException, HasAlreadyDoneException, ItemDoesNotExist, InvalidBorrowId, BorrowDoesNotExist{
                try{
                        user.completeLoan(borrowId);
                }catch ( BorrowDoesNotExist e){
                        if(hasBorrowId(borrowId)) throw new PermissionDeniedException("O término do empréstimo só pode ser confirmado pelo dono do item");
                        throw new BorrowDoesNotExist(e.getMessage());
                }
        }
        
        public String requisitarEmprestimo(User user, String idItem,int duracao) throws ItemDoesNotExist, ItemRequestAlreadyExistsException, WithoutPermissionRequestLoanException, InvalidDurationException, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver, IsNotOnwerException{
                return user.borrowItem(getItem(idItem), duracao);
        }
        
        public Item getItem(String itemId) throws ItemDoesNotExist{
                Item item = null;
                Iterator<User> it = listUsers.iterator();
                while(it.hasNext()){
                        User user = it.next();
                        try{
                                item = user.getItem(itemId);
                                break;
                        } catch (ItemDoesNotExist e){
                                if(!it.hasNext()) throw new ItemDoesNotExist(e.getMessage());
                        }
                }
                return item;
        }
        
        public User getOwner(String itemId) throws ItemDoesNotExist{
                User owner = null;
                Iterator<User> it = listUsers.iterator();
                while(it.hasNext()){
                        User user = it.next();
                        try{
                                user.getItem(itemId);
                                owner = user;
                                break;
                        } catch (ItemDoesNotExist e){
                                if(!it.hasNext()) throw new ItemDoesNotExist(e.getMessage());
                        }
                }
                return owner;
        }
        
        public String getAtributoItem(String idItem, String atributo) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
                return getOwner(idItem).getAtributoItem(getItem(idItem),atributo);
        }
        
        public void addItemInterest(User user, String itemId) throws ItemDoesNotExist, InvalidAtributException, PermissionDeniedException, HasAlreadyDoneException{
        	user.addItemInterest( getItem(itemId).getOwner(), getItem(itemId));
        }
        
        public void deleteItem(User user, String itemId) throws ItemDoesNotExist, IsNotOnwerException, PermissionDeniedException{
                user.deleteItem(getItem(itemId));
        }
        
        public void oferecerItem(User user,String idPedido,String itemId) throws InvalidAtributException, ItemDoesNotExist, InvalidSender, InvalidReceiver, InvalidMessage, InvalidSubject{
        	user.oferecerItem(getPedidoItem(idPedido), itemId);
        }
        
        public PublicarPedido getPedidoItem(String pedidoId) throws InvalidAtributException{
        	PublicarPedido pedido = null;
        	Iterator<User> it = listUsers.iterator();
            while(it.hasNext()){
                    User user = it.next();
                    try{
                    	pedido = user.getPedidoItem(pedidoId);
                    	break;
                    } catch (InvalidAtributException e){
                    	if(!it.hasNext()) throw new InvalidAtributException(e.getMessage());
                    }
            }
        	return pedido;
        }
        
        public String getStringMessages(User user, String idTopico) throws Exception{
        	boolean has = false;
        	for(User tmp: listUsers) try{tmp.getStringMessages(idTopico); has = true; }catch(Exception e){}
        	try {
        		return user.getStringMessages(idTopico);
        	}catch(InexistentTopic e){
        		if(has) throw new Exception("O usuário não tem permissão para ler as mensagens deste tópico");
        		throw new InexistentTopic(e.getMessage());
        	}
        }
}